package service

import (
	"context"
	"encoding/json"
	"io/ioutil"
	"strings"
	"time"

	"golang.org/x/sync/errgroup"

	"wxpay/pkg/util"

	"github.com/pkg/errors"

	"gorm.io/gorm"

	"github.com/aliyun/aliyun-oss-go-sdk/oss"

	"wxpay/internal/conf"
	"wxpay/pkg/alipay"

	"wxpay/pkg/merrors"

	"wxpay/internal/model"

	"wxpay/internal/dao"
	"wxpay/internal/dto"
)

type aliAgentService struct {
	d dao.DataStore
}

// NewAlipayAgentService 阿里代签约相关
func NewAlipayAgentService(d dao.DataStore) *aliAgentService {
	return &aliAgentService{d: d}
}

// 查询当面付签约
func (s *aliAgentService) FaceToFaceSignQuery(ctx context.Context, brandId, appId, bizNo, pid, authToken string) (*dto.AlipayAgentSignDTO, error) {
	if brandId == "" || bizNo == "" {
		return nil, nil
	}
	client, err := s.getAlipayClient(ctx, appId)
	if err != nil {
		return nil, err
	}
	r, fErr := s.d.AliFaceToFaceSign().QueryByBizNo(ctx, s.d.GetTx(ctx), brandId, bizNo)
	if val, ok := fErr.(model.NotFound); ok && val.NotExist() {
		//检查是否已经在支付宝后台做了进件
		res, err := client.signStatusQuery(ctx, authToken, pid, []string{"I1011000100000000001"})
		if err != nil {
			return nil, err
		}
		if len(res) > 0 && res[0].Status != "none" && res[0].Status != "invalid" {
			return &dto.AlipayAgentSignDTO{
				ApplyState:     "CANT_APPLY",
				ApplyStateDesc: "已经存在其他系统进件记录",
			}, nil
		}
		return &dto.AlipayAgentSignDTO{
			ApplyState:     "NOT_APPLIED",
			ApplyStateDesc: "未申请当面付",
		}, nil
	}
	if r == nil {
		return nil, errors.New("查询错误")
	}
	// 强制检查状态
	if !r.IsFinalState() {
		audit, err := client.orderQuery(ctx, r.BatchNo)
		if err != nil {
			return nil, err
		}
		if r.IsNewState(audit.OrderStatus) {
			// 持久化申请结果
			err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
				mAudit := dto.AdaptAliSignAuditDetail(audit)
				r.UpdateAuditState(mAudit)
				if err = s.d.AliFaceToFaceSign().Save(ctx, tx, r); err != nil {
					return err
				}
				// 添加查询定时任务
				if !r.IsFinalState() {
					if err = s.createAliFaceToFaceSignQueryTask(ctx, tx, r); err != nil {
						return err
					}
				}
				return nil
			})
		}
	}
	return dto.ConvertAliFaceToFaceSignDTO(r), nil
}

// 当面付签约申请
func (s *aliAgentService) FaceToFaceSignSubmit(ctx context.Context, apply *dto.AlipayAgentSignDTO) (*dto.AlipayAgentSignDTO, error) {
	if apply == nil {
		return nil, nil
	}
	apply.SignType = "facetoface"
	r, fErr := s.d.AliFaceToFaceSign().QueryByBizNo(ctx, s.d.GetTx(ctx), apply.BrandID, apply.BizApplyNo)
	if fErr == nil {
		return dto.ConvertAliFaceToFaceSignDTO(r), nil
	}
	if val, ok := fErr.(model.NotFound); ok && val.NotExist() {
		// 不存在申请单则需要创建
		// 生成申请编号
		apply.ApplyNo = GenApplyNo()
		return s.submitFaceToFaceSign(ctx, apply)
	}
	return nil, fErr
}

// 修改申请后重新提交
func (s *aliAgentService) FaceToFaceSignReSubmit(ctx context.Context, apply *dto.AlipayAgentSignDTO) (*dto.AlipayAgentSignDTO, error) {
	const op = "applymentService.FaceToFaceSignReSubmit"
	apply.SignType = "facetoface"
	// 幂等检查
	r, fErr := s.d.AliFaceToFaceSign().QueryByBizNo(ctx, s.d.GetTx(ctx), apply.BrandID, apply.BizApplyNo)
	if val, ok := fErr.(model.NotFound); ok && val.NotExist() {
		// 不存在申请单则报错
		return nil, merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "找不到申请单")
	}
	if fErr != nil {
		return nil, fErr
	}
	if r.SignType != apply.SignType {
		return nil, merrors.E(op, merrors.Invalid, "不是当面付申请单")
	}
	// 已存在申请单，在这个基础上重新提交
	apply.ID = r.ID
	apply.ApplyNo = r.ApplyNo
	apply.CreateTime = r.CreateTime
	return s.submitFaceToFaceSign(ctx, apply)
}

// 签约进度查询
func (s *aliAgentService) ExecuteFaceToFaceSignQueryTask(ctx context.Context, task *model.AliFaceToFaceSignQueryTask) error {
	const op merrors.Op = "aliAgentService.ExecuteFaceToFaceSignQueryTask"
	if task == nil {
		return nil
	}
	m, err := s.d.AliFaceToFaceSign().QueryByApplyNo(ctx, s.d.GetTx(ctx), task.ApplyNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		return nil
	}
	if err != nil {
		return nil
	}
	err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		apply, err := s.d.Lock().LockAliFaceToFaceSign(ctx, tx, m.ID)
		if err != nil {
			return err
		}
		client, err := s.getAlipayClient(ctx, apply.AppId)
		if err != nil {
			return err
		}
		audit, err := client.orderQuery(ctx, apply.BatchNo)
		if err != nil {
			return merrors.E(op, merrors.Invalid, merrors.ErrAlipayApi, err)
		}
		mAudit := dto.AdaptAliSignAuditDetail(audit)
		if apply.IsNewState(audit.OrderStatus) {
			apply.UpdateAuditState(mAudit)
			if err = s.d.AliFaceToFaceSign().Save(ctx, tx, apply); err != nil {
				return err
			}
		}
		if !apply.IsFinalState() {
			if err = s.createAliFaceToFaceSignQueryTask(ctx, tx, apply); err != nil {
				return err
			}
		}
		return nil
	})
	return err
}

func (s *aliAgentService) createAliFaceToFaceSignQueryTask(ctx context.Context, tx *gorm.DB, apply *model.AlipayAgentSign) error {
	payload := model.NewAliFaceToFaceSignQueryTask(apply)
	ps, _ := json.Marshal(&payload)
	// 延后1个小时执行
	msg := model.NewMessage(model.TopicAliFaceToFaceSignQuery, ps, time.Hour)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

// 提交申请
func (s *aliAgentService) submitFaceToFaceSign(ctx context.Context, apply *dto.AlipayAgentSignDTO) (*dto.AlipayAgentSignDTO, error) {
	if apply == nil {
		return nil, nil
	}
	const op merrors.Op = "aliAgentService.submitFaceToFaceSign"
	m := dto.AdaptAliFaceToFaceSign(apply)
	// 校验业务字段完整性
	if err := m.Validate(); err != nil {
		return nil, merrors.E(op, merrors.Invalid, merrors.InvalidArgument, err)
	}
	// 提交申请至支付宝

	client, err := s.getAlipayClient(ctx, apply.AppID)
	if err != nil {
		return nil, err
	}
	apply, err = client.faceToFaceSignSubmit(ctx, apply)
	if err != nil {
		return nil, err
	}
	// 查询申请详情
	audit, err := client.orderQuery(ctx, apply.BatchNo)
	if err != nil {
		return nil, err
	}
	// 持久化申请结果
	err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		mApply := dto.AdaptAliFaceToFaceSign(apply)
		mAudit := dto.AdaptAliSignAuditDetail(audit)
		mApply.UpdateAuditState(mAudit)
		if err = s.d.AliFaceToFaceSign().Save(ctx, tx, mApply); err != nil {
			return err
		}
		// 添加查询定时任务
		if err = s.createAliFaceToFaceSignQueryTask(ctx, tx, mApply); err != nil {
			return err
		}
		apply = dto.ConvertAliFaceToFaceSignDTO(mApply)
		return nil
	})
	if err != nil {
		return nil, err
	}
	return apply, nil
}

// 支付宝产品签约状态查询
func (s *aliAgentService) AlipaySignStatusQuery(ctx context.Context, appId, authToken, pid string, productCodes []string) ([]*dto.AliSignStatus, error) {
	// 提交申请至支付宝
	client, err := s.getAlipayClient(ctx, appId)
	if err != nil {
		return nil, err
	}
	return client.signStatusQuery(ctx, authToken, pid, productCodes)
}

func downImageFile(uri string) (*alipay.FileData, error) {
	if uri == "" {
		return nil, nil
	}
	image, fileName, err := util.DownloadFile(uri)
	if err != nil {
		return nil, err
	}
	return alipay.NewFileData(fileName, image), nil
}

func getImageFile(objName string) (*alipay.FileData, error) {
	if objName == "" {
		return nil, nil
	}
	// 如果是图片地址，直接下载
	if strings.HasPrefix(strings.ToLower(objName), "http") {
		return downImageFile(objName)
	}
	// 创建OSSClient实例
	client, _ := oss.New(conf.Conf.Oss.Endpoint, conf.Conf.Oss.AccessKeyID, conf.Conf.Oss.AccessKeySecret)
	// 获取存储空间
	bucket, _ := client.Bucket(conf.Conf.Oss.BucketName)

	body, err := bucket.GetObject(objName)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = body.Close()
	}()
	data, err := ioutil.ReadAll(body)
	if err != nil {
		return nil, err
	}
	return alipay.NewFileData(objName, data), nil
}

type agentClient struct {
	client *alipay.AlipayClient
}

func (s *aliAgentService) getAlipayClient(ctx context.Context, appId string) (*agentClient, error) {
	client, err := NewMchService(s.d).BuildAlipayAgentClient(ctx, appId)
	if err != nil {
		return nil, err
	}
	return &agentClient{client: client}, nil
}

func (a *agentClient) agentCreate(ctx context.Context, account string, contact *dto.AliSignContactInfoDTO) (string, error) {
	// 开启代签约事务
	cReq := &alipay.AgentCreateReq{
		Account: account,
		ContactInfo: &alipay.ContactInfo{
			ContactName:   contact.ContactName,
			ContactMobile: contact.ContactMobile,
			ContactEmail:  contact.ContactEmail,
		},
	}
	res, err := a.client.AgentCreate(ctx, cReq)
	if err != nil {
		return "", err
	}
	return res.BatchNo, nil
}

// 代签约事务提交
func (a *agentClient) agentConfirm(ctx context.Context, batchNo string) error {
	// 成功，提交签约事务
	req := &alipay.AgentConfirmReq{
		BatchNo: batchNo,
	}
	_, err := a.client.AgentConfirm(ctx, req)
	return err
}

func (a *agentClient) agentCancel(ctx context.Context, batchNo string) error {
	req := &alipay.AgentCancelReq{
		BatchNo: batchNo,
	}
	return a.client.AgentCancel(ctx, req)
}

func (a *agentClient) faceToFaceSignSubmit(ctx context.Context, apply *dto.AlipayAgentSignDTO) (*dto.AlipayAgentSignDTO, error) {
	if apply == nil {
		return nil, nil
	}
	batchNo, err := a.agentCreate(ctx, apply.AlipayAccount, apply.ContactInfo)
	if err != nil {
		return nil, err
	}
	// 提交当面付待签约
	sReq := &alipay.AgentFaceToFaceSignReq{
		BatchNo:           batchNo,
		MccCode:           apply.SignInfo.MccCode,
		Rate:              apply.SignInfo.Rate,
		SignAndAuth:       apply.SignInfo.SignAndAuth,
		BusinessLicenseNo: apply.SignInfo.BusinessLicenseNo,
		LongTerm:          apply.SignInfo.LongTerm,
		DateLimitation:    apply.SignInfo.DateLimitation,
	}
	var g errgroup.Group
	g.Go(func() error {
		sReq.SpecialLicensePic, err = getImageFile(apply.SignInfo.SpecialLicensePic)
		return err
	})
	g.Go(func() error {
		sReq.BusinessLicensePic, err = getImageFile(apply.SignInfo.BusinessLicensePic)
		return err
	})
	g.Go(func() error {
		sReq.BusinessLicenseAuthPic, err = getImageFile(apply.SignInfo.BusinessLicenseAuthPic)
		return err
	})
	g.Go(func() error {
		sReq.ShopScenePic, err = getImageFile(apply.SignInfo.ShopScenePic)
		return err
	})
	g.Go(func() error {
		sReq.ShopSignBoardPic, err = getImageFile(apply.SignInfo.ShopSignBoardPic)
		return err
	})
	if err = g.Wait(); err != nil {
		return nil, err
	}
	err = a.client.AgentFaceToFaceSign(ctx, sReq)
	// 如果失败，取消代签约事务
	if err != nil {
		if errC := a.agentCancel(ctx, batchNo); errC != nil {
			err = errors.Wrap(err, errC.Error())
		}
		return nil, err
	}
	// 提交签约
	err = a.agentConfirm(ctx, batchNo)
	if err != nil {
		_ = a.agentCancel(ctx, batchNo)
		return nil, err
	}
	apply.BatchNo = batchNo
	return apply, nil
}

// 代签约详情查询
func (a *agentClient) orderQuery(ctx context.Context, batchNo string) (*dto.AliSignAuditDetailDTO, error) {
	req := &alipay.AgentOrderQueryReq{
		BatchNo: batchNo,
	}
	res, err := a.client.AgentOrderQuery(ctx, req)
	if err != nil {
		return nil, err
	}
	return &dto.AliSignAuditDetailDTO{
		OrderStatus:  res.OrderStatus,
		ConfirmUrl:   res.ConfirmUrl,
		AgentAppID:   res.AgentAppId,
		RejectReason: res.RejectReason,
		MerchantPID:  res.MerchantPid,
	}, nil
}

// 签约状态查询
func (a *agentClient) signStatusQuery(ctx context.Context, authToken, pid string, productCodes []string) ([]*dto.AliSignStatus, error) {
	req := &alipay.AgentSignStatusQueryReq{
		AppAuthToken: authToken,
		Pid:          pid,
		ProductCodes: productCodes,
	}
	resp, err := a.client.AgentSignStatusQuery(ctx, req)
	if err != nil {
		return nil, err
	}
	ret := make([]*dto.AliSignStatus, len(resp.SignStatusList))
	for idx, r := range resp.SignStatusList {
		t := &dto.AliSignStatus{
			ProductName: r.ProductName,
			ProductCode: r.ProductCode,
			Status:      r.Status,
			StatusDesc:  r.StatusDesc,
		}
		ret[idx] = t
	}
	return ret, nil
}
